Skip to content

feat(web-shell): surface worktree isolation in the new-session empty state - #7365

Merged
wenshao merged 5 commits into
QwenLM:mainfrom
wenshao:feat/webshell-worktree-toggle
Jul 21, 2026
Merged

feat(web-shell): surface worktree isolation in the new-session empty state#7365
wenshao merged 5 commits into
QwenLM:mainfrom
wenshao:feat/webshell-worktree-toggle

Conversation

@wenshao

@wenshao wenshao commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

This PR moves the worktree-isolated session entry point out of the sidebar's git-branch pill dropdown — where it was hard to discover — and into the chat empty state, which is the de-facto "new session" page. A visible toggle now appears below the welcome header whenever the target workspace is trusted and is a git repository. Turning it on shows the existing pending badge (now with a cancel button); the first prompt then creates the session in an isolated worktree, reusing the existing pending-worktree state machine and lazy session creation, so there are no SDK or daemon changes. The toggle and badge also get a more polished visual treatment (icon medallion, hover micro-interactions, entrance animation) and concise copy that no longer wraps.

The sidebar git pill is simplified as well: the now-redundant "New worktree task" menu item is removed, and because that left a single-item dropdown, the pill now opens the changes view directly on click instead of opening a menu.

Why it's needed

The worktree isolation feature was effectively hidden — reaching it required knowing that the git-branch pill in the workspace header was clickable and then finding the item in a dropdown that only rendered under several conditions. Putting the entry point in the empty state makes it discoverable at the exact moment a user starts a new session, which is when the decision to isolate belongs. Consolidating on one entry point also lets the sidebar git pill return to a single, obvious action.

Reviewer Test Plan

How to verify

  • Open the web shell against a trusted git workspace and start a new session (empty state). Confirm a "Worktree isolated session" toggle appears below the welcome header; hover it to see the lift / glow / icon micro-interaction.
  • Click the toggle: confirm it is replaced by the purple pending badge with a cancel (✕) button. Click cancel: confirm the toggle returns.
  • With the toggle on, send the first prompt and confirm the session is created in an isolated worktree (the git chip shows the worktree branch and the session row in the sidebar shows the fork badge).
  • Confirm the toggle does NOT appear for an untrusted workspace or a non-git directory.
  • In the sidebar, click the git-branch pill on a trusted repo and confirm it opens the changes view directly (no dropdown, no "New worktree task" item).

Evidence (Before & After)

Worktree entry point — before: buried in the sidebar git-pill dropdown. After: a visible toggle in the new-session empty state.

Toggle (off) Enabled (on)
worktree toggle off worktree badge on

Close-ups:

Toggle chip Badge chip
toggle chip badge chip

Tested on

OS Status
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

Environment (optional)

npm run dev:daemon (real daemon + vite web-shell) for manual verification; Playwright mock-daemon E2E and vitest for automated coverage (web-shell suite: 1948 passed).

Risk & Scope

  • Main risk or tradeoff: the toggle reuses the existing pending-worktree path, so creation semantics are unchanged; the new gating (trusted + git repo) mirrors the old sidebar entry, so no new failure surface is exposed.
  • Not validated / out of scope: no merge-back UI; behavior when the user enables the toggle and then switches the composer to a non-git workspace is unchanged from the prior sidebar flow (the daemon rejects and the badge stays for retry).
  • Breaking changes / migration notes: none. The onNewSession worktree option (a public sidebar prop) is preserved; only the internal sidebar menu entry was removed.

Linked Issues

中文说明

这个 PR 做了什么

本 PR 把 worktree 隔离会话的入口从侧边栏 git 分支胶囊的下拉菜单(很难被发现)移到了聊天空状态——也就是事实上的"新建会话"页。只要目标 workspace 已信任且是 git 仓库,欢迎语下方就会出现一个可见的开关。开启后显示现有的 pending 徽标(现在带取消按钮);发送首条消息时会复用现有的 pending-worktree 状态机和懒创建链路,在隔离 worktree 中创建会话,因此没有 SDK 或 daemon 改动。开关和徽标也做了更精致的视觉处理(图标徽章、悬停微交互、入场动画),文案改短后不再换行。

侧边栏 git 胶囊也一并简化:删掉了现在已冗余的"新建 worktree 任务"菜单项;由于只剩一个选项,胶囊现在点击直接打开"查看变更",不再弹下拉菜单。

为什么需要

worktree 隔离功能实际上被藏起来了——要用它得先知道 workspace 头部的 git 分支胶囊可以点击,再在一个只有满足多个条件才渲染的下拉里找到那一项。把入口放到空状态,能让用户在开始新会话的那一刻(正是决定是否隔离的时机)就发现它。收敛到单一入口也让侧边栏 git 胶囊回归到一个明确的操作。

审阅测试计划

如何验证

  • 对着一个已信任的 git workspace 打开 web shell 并新建会话(空状态)。确认欢迎语下方出现"Worktree 隔离会话"开关;悬停可见上浮 / 发光 / 图标微交互。
  • 点击开关:确认它被紫色 pending 徽标(带 ✕ 取消按钮)替换。点击取消:确认开关恢复。
  • 开启开关后发送首条消息,确认会话在隔离 worktree 中创建(git 胶囊显示 worktree 分支,侧边栏会话行显示分叉徽标)。
  • 确认未信任 workspace 或非 git 目录下开关不出现。
  • 在侧边栏点击已信任仓库的 git 分支胶囊,确认直接打开"查看变更"(无下拉、无"新建 worktree 任务"项)。

证据(前后对比)

worktree 入口——之前:藏在侧边栏 git 胶囊下拉里;之后:新建会话空状态里的可见开关。截图见上方英文版。

测试环境

OS 状态
🍏 macOS
🪟 Windows ⚠️
🐧 Linux ⚠️

环境(可选)

手动验证用 npm run dev:daemon(真实 daemon + vite web-shell);自动化覆盖用 Playwright mock-daemon E2E 和 vitest(web-shell 套件 1948 通过)。

风险与范围

  • 主要风险 / 权衡:开关复用现有 pending-worktree 路径,创建语义不变;新的门控(已信任 + git 仓库)与原侧边栏入口一致,不引入新的失败面。
  • 未验证 / 超出范围:不做 merge-back UI;用户开启开关后在 composer 切到非 git workspace 的行为与原侧边栏流程一致(daemon 拒绝,徽标保留供重试)。
  • 破坏性变更 / 迁移说明:无。onNewSession 的 worktree 选项(侧边栏对外 prop)保留;只移除了内部侧边栏菜单项。

关联 Issue

wenshao and others added 2 commits July 21, 2026 06:49
…state

The worktree-isolated session entry was buried in the sidebar git-branch
pill dropdown, making it hard to discover. Add a visible toggle to the
chat empty state — the de-facto new-session page — that reuses the
existing pending-worktree state machine and lazy session creation, so no
SDK or daemon changes are needed. Enabling it shows the pending badge
with a cancel affordance; the first prompt then creates the session in an
isolated worktree. The toggle is offered only when the target workspace
is trusted and is a git repository, mirroring the sidebar entry gating.

Also simplify the sidebar git pill: drop the now-redundant "New worktree
task" item and make the pill open the changes view directly instead of a
single-item dropdown.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Thanks for the PR!

Template looks good ✓

Problem: the worktree isolation entry point was buried in the sidebar git-pill dropdown — a real discoverability issue with an existing feature, not a theoretical concern. Before/after screenshots clearly show the old vs new placement.

Direction: aligned. Surfacing an existing feature at the moment of session creation is a straightforward UX improvement. CHANGELOG shows worktree isolation is a well-established feature area with ongoing fixes upstream, so improving its discoverability is relevant.

Size: not applicable — all changes are in packages/web-shell/client/, no core module paths touched.

Approach: the scope feels right. Reusing the existing pendingWorktreeRef / worktreePending state machine means no SDK or daemon changes — the toggle just sets a pending intent. The sidebar simplification (dropdown → direct button) follows naturally from removing the worktree menu item. One observation: the CSS is fairly elaborate (entrance animations, hover glow, micro-interactions) — not a blocker, just noting it's more visual polish than the minimal path would require.

Moving on to code review. 🔍

中文说明

感谢贡献!

模板完整 ✓

问题:worktree 隔离入口藏在侧边栏 git 胶囊下拉菜单里——这是已有功能的真实可发现性问题,不是理论性担忧。前后截图清楚展示了新旧入口位置。

方向:对齐。在会话创建时暴露已有功能是直接的 UX 改进。CHANGELOG 显示 worktree 隔离是成熟的功能领域,上游有持续修复,改善其可发现性是相关的。

规模:不适用——所有改动在 packages/web-shell/client/,未触及核心模块路径。

方案:范围合理。复用现有 pendingWorktreeRef / worktreePending 状态机意味着无 SDK 或 daemon 改动——开关只设置 pending 意图。侧边栏简化(下拉→直接按钮)是移除 worktree 菜单项后的自然结果。一个观察:CSS 相当精致(入场动画、悬停发光、微交互)——不是阻塞项,只是 noting 这比最小路径需要更多视觉打磨。

进入代码审查 🔍

Qwen Code · qwen3.7-max

Reviewed at e19f022f75f58904c14e24b856f45f412da294cf · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head 722622f. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

worktree-empty-state-dark before/after

worktree-empty-state-enabled-dark before/after

worktree-empty-state-enabled-light before/after

worktree-empty-state-light before/after

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Code Review

Independent proposal: given "worktree entry point is buried in a sidebar dropdown", I would add a toggle in the chat empty state that sets the existing pendingWorktreeRef intent, gate it on trusted + git repo (same as the sidebar), add a cancel button to the pending badge, and simplify the now-single-item dropdown to a direct button.

Comparison: the PR does exactly this. The implementation reuses the existing pending-worktree state machine (pendingWorktreeRef / worktreePending) with no SDK or daemon changes — the toggle is purely a UI intent setter. The sidebar simplification follows naturally.

No critical blockers found. Specific observations:

  • App.tsxworktreeToggleEligible is a plain expression (not memoized), which is fine for a small array find + property check. The handleEnableWorktree / handleCancelWorktree callbacks correctly use empty deps (only refs and state setters). The welcomeHeader memo includes all new dependencies.
  • WorkspaceSection.tsx — the DropdownMenu<button> swap is clean. The onNewWorktreeSession prop removal is safe: no consumers outside packages/web-shell/. The sidebar.newWorktreeTask i18n key is correctly kept — it's still used as an aria-label for the fork icon badge on worktree sessions (line 2571 of WebShellSidebar.tsx).
  • i18n.tsx — new keys (worktree.toggleHint, worktree.cancel) defined in both EN and ZH. The shortened worktree.welcomeDesc still conveys the key information.
  • CSS — 154 new lines in App.module.css for the toggle/badge styling (entrance animation, hover glow, icon medallion). More visual polish than the minimal path, but appropriate for a user-facing UI feature.
  • Tests — 6 new unit tests in App.test.tsx (visibility gating for trusted/untrusted/non-git, toggle on/off, session creation with/without worktree), 3 new Playwright E2E tests, and updated WorkspaceSection test. Coverage looks solid.
  • mockDaemon.ts — adds GET /workspaces/:cwd/git route support for E2E tests. Clean addition.

Typecheck passes cleanly (tsc --noEmit).

Real-Scenario Testing

This is a browser UI change (web-shell), so the real-scenario evidence comes from Playwright E2E tests driving a real Chromium browser against the actual web-shell UI with a mock daemon:

Running 3 tests using 1 worker

  ✓  1 [chromium] › client/e2e/web-shell.worktree-toggle.spec.ts:51:1 › enabling the worktree toggle sends worktree intent on session creation (4.3s)
  ✓  2 [chromium] › client/e2e/web-shell.worktree-toggle.spec.ts:78:1 › cancelling the toggle omits worktree on session creation (2.0s)
  ✓  3 [chromium] › client/e2e/web-shell.worktree-toggle.spec.ts:102:1 › toggle is hidden when the workspace is not a git repository (1.0s)

  3 passed (9.1s)

Unit tests (vitest):

 App.test.tsx — 149 passed (149)  [includes 6 new worktree toggle tests]
 WorkspaceSection.test.tsx — 7 passed (7)

 Test Files  2 passed (2)
      Tests  156 passed (156)

Dev server starts and serves correctly:

  VITE v5.4.21  ready in 212 ms

  ➜  Local:   http://localhost:5199/
  ➔  Network: use --host to expose
中文说明

代码审查

独立方案: 给定"worktree 入口藏在侧边栏下拉里"的问题,我会在聊天空状态添加一个开关来设置现有的 pendingWorktreeRef 意图,以 trusted + git repo 门控(与侧边栏一致),给 pending 徽标加取消按钮,并把只剩一项的下拉简化为直接按钮。

对比: PR 完全这样做了。实现复用现有 pending-worktree 状态机(pendingWorktreeRef / worktreePending),无 SDK 或 daemon 改动——开关纯粹是 UI 意图设置器。侧边栏简化是自然结果。

未发现关键阻塞项。具体观察:

  • App.tsxworktreeToggleEligible 是普通表达式(未 memoize),对小数组 find + 属性检查来说没问题。handleEnableWorktree / handleCancelWorktree 回调正确使用空依赖(只用 ref 和 state setter)。welcomeHeader memo 包含所有新依赖。
  • WorkspaceSection.tsxDropdownMenu<button> 替换干净。onNewWorktreeSession prop 移除安全:packages/web-shell/ 外无消费者。sidebar.newWorktreeTask i18n key 正确保留——仍用作 worktree 会话分叉图标的 aria-label
  • i18n.tsx — 新 key(worktree.toggleHintworktree.cancel)在 EN 和 ZH 中均已定义。缩短的 worktree.welcomeDesc 仍传达关键信息。
  • CSS — App.module.css 新增 154 行用于开关/徽标样式(入场动画、悬停发光、图标徽章)。比最小路径更多视觉打磨,但对面向用户的 UI 功能来说合适。
  • 测试 — App.test.tsx 新增 6 个单元测试(trusted/untrusted/non-git 可见性门控、开关切换、带/不带 worktree 的会话创建),3 个新 Playwright E2E 测试,以及更新的 WorkspaceSection 测试。覆盖扎实。
  • mockDaemon.ts — 为 E2E 测试添加 GET /workspaces/:cwd/git 路由支持。干净的添加。

类型检查通过(tsc --noEmit)。

真实场景测试

这是浏览器 UI 变更(web-shell),真实场景证据来自 Playwright E2E 测试——驱动真实 Chromium 浏览器 against 实际 web-shell UI(mock daemon):

Running 3 tests using 1 worker

  ✓  1 [chromium] › enabling the worktree toggle sends worktree intent on session creation (4.3s)
  ✓  2 [chromium] › cancelling the toggle omits worktree on session creation (2.0s)
  ✓  3 [chromium] › toggle is hidden when the workspace is not a git repository (1.0s)

  3 passed (9.1s)

单元测试(vitest):

 App.test.tsx — 149 通过 (149)  [含 6 个新 worktree toggle 测试]
 WorkspaceSection.test.tsx — 7 通过 (7)

 Test Files  2 通过 (2)
      Tests  156 通过 (156)

开发服务器正常启动和响应。

Qwen Code · qwen3.7-max

Reviewed at e19f022f75f58904c14e24b856f45f412da294cf · re-run with @qwen-code /triage

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — clean, focused UI improvement with solid test coverage; only non-blocking nit is the elaborate CSS polish.

This PR does one thing well: it takes an existing feature that was effectively hidden (worktree isolation, buried in a sidebar dropdown that only rendered under several conditions) and puts it where users actually start a session — the empty state. The implementation is the simplest possible path: the toggle just sets the existing pendingWorktreeRef intent, reusing the entire pending-worktree state machine and lazy session creation. No SDK changes, no daemon changes, no new failure surface.

The sidebar simplification (dropdown → direct button) is a natural consequence of removing the worktree menu item — leaving a single-item dropdown would be worse UX than a direct click. The onNewWorktreeSession prop removal is clean with no external consumers.

Tests are comprehensive: 6 unit tests covering all gating conditions and the toggle lifecycle, 3 Playwright E2E tests driving a real browser, and the existing 156-test suite passes without regression. Typecheck is clean.

The CSS is more elaborate than strictly necessary (entrance animations, hover glow, icon medallion), but for a user-facing discoverability feature, visual polish is part of the deliverable — not scope creep.

Approving. ✅

中文说明

置信度:4/5 — 干净、聚焦的 UI 改进,测试覆盖扎实;唯一非阻塞的小点是 CSS 打磨较精致。

这个 PR 做好了一件事:把一个实际被隐藏的功能(worktree 隔离,藏在需要满足多个条件才渲染的侧边栏下拉里)放到用户真正开始会话的地方——空状态。实现是最简路径:开关只设置现有的 pendingWorktreeRef 意图,完全复用 pending-worktree 状态机和懒创建链路。无 SDK 改动、无 daemon 改动、无新失败面。

侧边栏简化(下拉→直接按钮)是移除 worktree 菜单项的自然结果——只剩一项的下拉不如直接点击。onNewWorktreeSession prop 移除干净,无外部消费者。

测试全面:6 个单元测试覆盖所有门控条件和开关生命周期,3 个 Playwright E2E 测试驱动真实浏览器,现有 156 个测试套件无回归通过。类型检查干净。

CSS 比严格必要更精致(入场动画、悬停发光、图标徽章),但对面向用户的可发现性功能来说,视觉打磨是交付物的一部分——不是范围蔓延。

批准。✅

Qwen Code · qwen3.7-max

Reviewed at e19f022f75f58904c14e24b856f45f412da294cf · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, looks ready to ship. ✅

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers. Suggestions are inline.

— qwen3.7-max via Qwen Code /review


describe('WorkspaceSection git chip', () => {
it('renders a git chip inside a dropdown trigger for a trusted repo', async () => {
it('renders a clickable git chip for a trusted repo', async () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The test name promises "clickable" but the body only checks DOM attributes (tagName, data-dirty, className, aria-label) — it never clicks the <button> or asserts onOpenGitDiff was called. A future miswire of the onClick handler would go undetected.

Failure scenario: a refactor wraps the button in a <div> that swallows click events, or accidentally deletes the onClick prop. The test still passes because it only verifies the chip rendered, not that it responded to interaction.

Suggested change
it('renders a clickable git chip for a trusted repo', async () => {
it('renders a clickable git chip for a trusted repo', async () => {
render(<WorkspaceSection {...baseProps} />);
const chip = screen.getByText('main');
expect(chip.closest('[data-dirty]')).toHaveAttribute('data-dirty', 'false');
const button = chip.closest('button');
expect(button).toHaveClass(styles.gitPill);
expect(button).toHaveAttribute('aria-label', 'View changes');
fireEvent.click(button!);
expect(baseProps.onOpenGitDiff).toHaveBeenCalledWith('/tmp/project');
});

— qwen3.7-max via Qwen Code /review

Comment on lines 3546 to 3547
onOpenGitDiff={onOpenGitDiff}
onNewWorktreeSession={(cwd) =>
handleNewSession(ws.primary ? undefined : cwd, {
worktree: {},
})
}
formatTime={(iso) => formatRelativeTime(iso, t)}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] The worktree option on createNewSession / handleNewSession / onNewSession is now dead code — this diff removed the only caller that passed { worktree: {} }. The new toggle sets pendingWorktreeRef.current directly, bypassing this path entirely.

Failure scenario: a future developer reads the opts?: { worktree?: { slug?: string } } type, assumes it's a live API, and writes a caller through createNewSession. It compiles and type-checks, but the code path has zero integration test coverage since this PR removed the sole exerciser.

Suggested fix: remove worktree from the opts type on createNewSession, handleNewSession, and the onNewSession prop signature. Keep opts for keepView.

— qwen3.7-max via Qwen Code /review

@wenshao

wenshao commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /takeover

@qwen-code-dev-bot qwen-code-dev-bot added the autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+) label Jul 20, 2026
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤝 Takeover engaged: the autofix loop now manages this PR — it will address new review feedback and resolve base conflicts until the label is removed or the round cap is reached. Remove the autofix/takeover label (or comment @qwen-code /takeover stop) to release.

中文说明

🤝 已接管:autofix 循环现在管理此 PR —— 将持续处理新的评审反馈与 base 冲突,直到移除标签或达到轮次上限。移除 autofix/takeover 标签(或评论 @qwen-code /takeover stop)即可释放。

…uite

The worktree toggle lives in the new-session empty state, and every visuals
scenario navigates to /session/:id via gotoSession — so the suite had never
rendered the empty state at all, and the before/after preview reported "no
screenshot changes" for this PR despite the new UI.

Add a `gotoNewSession` harness helper (primes the theme, lands on `/`, asserts
the theme took effect; no replay to settle) and a `worktree empty state`
scenario using the git-ready workspace this PR already made mockable
(`gitStatus` + the /workspaces/:cwd/git route). It captures both states — the
offered toggle and, after clicking, the pending-worktree badge with its cancel
affordance — and asserts the swap, so a regression fails an assertion rather
than only differing in the screenshot. All four captures are byte-stable
across runs (0% pixel diff).

The helper also closes the structural gap: any future empty-state work
(onboarding copy, first-run affordances) now has a way into the preview.
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on:

Autofix review response — PR #7365

Addressed both inline suggestions from the automated reviewer. No conflict
(--conflict false), so origin/main was not merged. One additive commit:
6a9186e57.

Feedback point 1 — "clickable" git chip test never clicks

File: packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx:138
Classification: Optional (**[Suggestion]**) — accepted.

The test name promised a "clickable" chip but only asserted DOM attributes
(tagName, data-dirty, className, aria-label); it never exercised the
onClick handler, so a deleted or miswired handler would pass silently.

Change: kept the existing assertions and added an interaction check in the
file's own style (manual createRoot + act + DOM event dispatch — this suite
does not use Testing Library). The chip is a read-only <output>; the test now
locates the wrapping <button> via chip.closest('button'), dispatches a
bubbling click, and asserts onOpenGitDiff was called with the workspace cwd
(/tmp/project). I did not copy the reviewer's snippet verbatim because it
assumed Testing Library APIs and a data-dirty="false" state that contradicts
this test's unstaged: 1 fixture.

Feedback point 2 — dead worktree option on the new-session path

File: packages/web-shell/client/components/sidebar/WebShellSidebar.tsx:3547
Classification: Optional (**[Suggestion]**) — accepted.

This PR removed the only caller that passed { worktree: {} } (the former
onNewWorktreeSession prop); the empty-state toggle now sets
pendingWorktreeRef directly. The worktree field on the new-session option
type was therefore dead and invited a future caller through an untested path.

Change: removed worktree from the option type on the onNewSession prop
signature and on handleNewSession in WebShellSidebar.tsx, and from
createNewSession in App.tsx (keeping opts for keepView). The sidebar
handler is now (workspaceCwd) => createNewSession(workspaceCwd).

Behavior is preserved exactly: createNewSession previously ran
pendingWorktreeRef.current = opts?.worktree, and since no caller passed
worktree, that always evaluated to undefined. Those two lines are now an
explicit pendingWorktreeRef.current = undefined; setWorktreePending(false);
(with a short comment explaining why a fresh chat drops the toggle's pending
intent), so a plain "new chat" still clears any pending worktree exactly as
before.

Verification

  • npm run typecheck — pass
  • npm run lint — pass
  • npm run build — pass
  • Focused Vitest (web-shell): WorkspaceSection.test.tsx 7/7 pass;
    App.test.tsx + WebShellSidebar.workspace-removal.test.tsx +
    WebShellSidebar.collapse-persist.test.tsx 180/180 pass

No settings source changed, so no settings-schema regeneration was required.

中文说明

Autofix 评审回应 — PR #7365

已处理自动化评审提出的两条行内建议。无冲突(--conflict false),因此未合并
origin/main。新增一个提交:6a9186e57

反馈点 1 — “可点击”的 git 分支标签测试从未真正点击

文件: packages/web-shell/client/components/sidebar/WorkspaceSection.test.tsx:138
分类: 可选(**[Suggestion]**)— 已采纳。

该测试名称声称验证“可点击”的分支标签,但只断言了 DOM 属性(tagName
data-dirtyclassNamearia-label),从未触发 onClick 处理函数,因此即使
处理函数被删除或接错线,测试也会静默通过。

改动: 保留原有断言,并以该文件自身的风格(手动 createRoot + act + DOM
事件分发——本测试套件不使用 Testing Library)新增交互验证。分支标签本身是一个只读
<output>;测试现在通过 chip.closest('button') 找到外层的 <button>,分发一个
冒泡的 click 事件,并断言 onOpenGitDiff 被以工作区 cwd(/tmp/project)调用。
我没有逐字照搬评审给出的代码片段,因为它假定使用 Testing Library 的 API,且使用了
data-dirty="false" 状态,与本测试 unstaged: 1 的固定数据相矛盾。

反馈点 2 — 新建会话路径上已失效的 worktree 选项

文件: packages/web-shell/client/components/sidebar/WebShellSidebar.tsx:3547
分类: 可选(**[Suggestion]**)— 已采纳。

本 PR 移除了唯一会传入 { worktree: {} } 的调用方(原先的
onNewWorktreeSession prop);空状态下的开关现在直接设置 pendingWorktreeRef
因此新建会话选项类型上的 worktree 字段已失效,并可能诱导未来的调用方走一条没有
测试覆盖的路径。

改动:WebShellSidebar.tsx 中的 onNewSession prop 签名和
handleNewSession 上,以及 App.tsx 中的 createNewSession 上移除了 worktree
选项(保留用于 keepViewopts)。sidebar 的处理函数现为
(workspaceCwd) => createNewSession(workspaceCwd)

行为完全保持不变:createNewSession 此前执行
pendingWorktreeRef.current = opts?.worktree,而由于没有任何调用方传入 worktree
其结果始终为 undefined。这两行现在改为显式的
pendingWorktreeRef.current = undefined; setWorktreePending(false);(并附一条简短
注释,说明为何新建会话会丢弃开关设置的待处理意图),因此普通“新建对话”仍会像之前
一样清除任何待处理的 worktree。

验证

  • npm run typecheck — 通过
  • npm run lint — 通过
  • npm run build — 通过
  • 聚焦 Vitest(web-shell):WorkspaceSection.test.tsx 7/7 通过;
    App.test.tsx + WebShellSidebar.workspace-removal.test.tsx +
    WebShellSidebar.collapse-persist.test.tsx 180/180 通过

未改动任何设置源文件,因此无需重新生成设置 schema。

Base-conflict check: no conflict with main.

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human.


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

qwen-code-ci-bot pushed a commit that referenced this pull request Jul 21, 2026

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed — no blockers. Suggestions are inline.

— qwen3.7-max via Qwen Code /review

Comment thread packages/web-shell/client/App.tsx
@wenshao

wenshao commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

Code review — surface worktree isolation in the new-session empty state

Overview

Moves the worktree-isolation entry point out of the sidebar git-pill dropdown and into the new-session empty state as a visible toggle. It reuses the existing pending-worktree state machine and lazy (first-prompt) session creation, so there are no SDK/daemon changes. The sidebar git pill is simplified from a dropdown to a single native button that opens the Changes view directly. Ships unit + Playwright E2E + visual-screenshot coverage, an i18n copy pass (en/zh), and a design doc.

I traced the core path and it holds up:

  • Toggle → pendingWorktreeRef.current = {} / setWorktreePending(true) → first prompt → sendPromptensureSessionForPrompt reads pendingWorktreeRef.current → creates the worktree session → finally clears the pending intent. The unit/E2E tests assert worktree: {} reaches the POST /session body. ✅
  • Eligibility (trusted && gitStatus.branch) is resolved against activeWorkspaceCwd, which is the same workspace resolution ensureSessionForPrompt uses at submit time (locked ?? selected ?? primary), and selectedWorkspaceGitStatus is fetched keyed on that same cwd — so the gate and the creation target can't diverge. ✅
  • welcomeHeader (and hence the toggle) is passed to MessageList only when isChatEmptyState, so the toggle can't leak into an active session even though the header row otherwise renders at the top of the transcript. ✅
  • createNewSession now unconditionally clears the pending intent; every remaining caller passes at most { keepView }, so no caller relies on the removed worktree opt. No orphaned i18n keys (sidebar.newWorktreeTask is still used by the session-row fork badge; sidebar.worktreeDescription and its only usage are both removed). ✅

Strengths

  • Genuinely low-risk: reuses the state machine and mirrors the old sidebar gating, so no new failure surface — matches the PR's risk statement.
  • Good test coverage across layers: unit (visibility gates, on/off, submit body with/without worktree), E2E (worktree intent in the request body, cancel path, non-git hidden), and a new gotoNewSession visual helper that is the suite's only view of the empty state — nice catch to keep it from being invisible to the preview.
  • Mock-daemon /workspaces/:cwd/git route defaults to a null-branch status, so it's additive: existing scenarios that don't set gitStatus keep hiding the chip exactly as before.

Findings (all minor — nothing blocking)

  1. Focus management on the toggle↔badge swap (a11y). Clicking the toggle unmounts it and mounts the badge; focus falls back to <body>. Same on cancel (✕). Keyboard/AT users lose their place both directions. Consider moving focus to the cancel button on enable and back to the toggle on cancel. — App.tsx

  2. Git-pill accessible name / tooltip (minor a11y regression). The new wrapper <button aria-label={t('gitDiff.title')}> gives the pill an explicit name ("Changes"), which overrides the inner <output aria-label="…branch main…"> — so the branch/dirty summary is dropped from the pill's accessible name (previously the role="button" span computed its name from that content). And because the tooltip trigger is the non-focusable <output>, the focusable element is now the outer button, so keyboard focus no longer surfaces the branch tooltip. Net it's still an upgrade (native button + single click beats span+dropdown), but consider folding the branch into the button's aria-label so the summary isn't lost. — WorkspaceSection.tsx

  3. Test robustness — hardcoded promise ticks. waitForToggle() awaits the git-status fetch with three fixed flush() calls. If that promise chain ever gains a tick, the unit tests break silently. Prefer vi.waitFor(() => expect(container.querySelector(toggleSelector)).not.toBeNull()). — App.test.tsx

  4. Test setup — good-git-repo default lives in afterEach, not beforeEach. The workspaceByCwd{ branch: 'main' } mock is restored in afterEach, so each test depends on the previous test's teardown to start from a working git repo. That's order-fragile and unconventional; establishing the default in beforeEach makes each test self-contained. — App.test.tsx

Nits

  • worktreeToggleEligible is recomputed unmemoized every render and feeds the welcomeHeader useMemo deps — fine in practice (cheap .find() returning a primitive), just noting.
  • The off-state control is a plain <button> rather than role="switch" / aria-pressed. Acceptable since it's a one-way reveal rather than a checkbox, but a screen reader announces "button", not a toggle state.

Verdict: Clean, well-tested, low-risk discoverability win. Recommend addressing the two a11y items (1, 2) and the test-robustness items (3, 4) as follow-up polish, none of which block merge.

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Could not address the latest feedback automatically (round 2/100). A human should take over this PR.

What I found before stopping:
Qwen failed during address-review: status 1.

See the Qwen Autofix agent step logs for model/tool output.

Run log: https://github.com/QwenLM/qwen-code/actions/runs/29791533733


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

wenshao pushed a commit that referenced this pull request Jul 21, 2026
#7365 stranded at round 2/100 on this render:

    [API Error: terminated (cause: read ECONNRESET)]

The connection to the model dropped mid-run. That is as transient as a 429, but
the classifier never saw it that way: a transport failure never got far enough
to have an HTTP status, so it fell through to the keyword arm, and the keyword
arm only knew about rate limits and quotas. It was classified terminal, the
watermark advanced, and a PR that needed nothing but a re-run was handed to a
human.

Verified against the shipped classifier before the fix — every transport render
came back terminal:

    terminated (cause: read ECONNRESET)   -> terminal
    fetch failed                          -> terminal
    socket hang up                        -> terminal
    connect ETIMEDOUT                     -> terminal

Adds a transport arm to the code-less branch: ECONNRESET, ECONNREFUSED,
ETIMEDOUT, EPIPE, EAI_AGAIN, socket hang up, fetch failed, terminated.

ENOTFOUND is deliberately excluded. A hostname that does not resolve is a
misconfigured endpoint, which repeats forever — the same reasoning that keeps a
bad model name terminal.

Coded errors are unaffected: the arm sits after the status-code branch, so the
400 short-circuit added in 719991a still runs first.
yiliang114 pushed a commit to yiliang114/qwen-code that referenced this pull request Jul 21, 2026
…wenLM#7375)

An empty visual preview means one of two opposite things: the change genuinely
moves no pixel, or no scenario renders the UI it touches. The bot printed the
same green check for both, so the second — a coverage gap, where the preview
literally cannot see the feature — read as a clean bill of health.

That has now happened three times (QwenLM#7035 primary label, QwenLM#7221 worktree badge,
QwenLM#7365 empty-state toggle), each caught only because a maintainer noticed the
missing image and asked. The signal to tell them apart was already there and
unused: the render workflow only runs when the web-shell client or webui source
changed, so an empty preview is by construction "UI code changed, nothing
rendered differently".

When no view changed, look at which files the PR touched. If any are
render-shaping (.tsx / .css / .svg under the rendered surface, excluding test
and scenario code), list them and say the result is ambiguous, with a pointer
to where a scenario goes. Otherwise keep the green check — a logic-only PR with
no visual delta is expected, and prompting there would train everyone to ignore
the prompt when it matters. The path list comes from the PR files API in the
privileged publish job, which never checks out PR code; if that call fails the
comment falls back to the current wording.

Co-authored-by: wenshao <wenshao@example.com>
@wenshao

wenshao commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator Author

@qwen-code /retry

@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🔄 AutoFix re-armed. The next scan re-reads this PR's feedback from the start and the round counter resets. Nothing was deleted — this marker supersedes the evaluation markers above it.

中文说明

🔄 已重新武装 AutoFix。下一次扫描会从头重新读取本 PR 的反馈,轮次计数也已重置。未删除任何内容 —— 本标记使其上方的评估标记失效。

- Move focus to cancel button on toggle enable and back on cancel (a11y)
- Include branch name in git-pill button aria-label (a11y)
- Replace hardcoded flush() ticks with vi.waitFor() in test helper
- Move git-repo mock default from afterEach to beforeEach
- Add test: sidebar New chat clears pending worktree intent
@qwen-code-dev-bot

Copy link
Copy Markdown
Collaborator

🤖 Addressed the latest review feedback (round 1/100). What changed, and what I pushed back on:

Review feedback addressed

Inline comments

[rc:3618264941] WorkspaceSection.test.tsx — test should click the button and assert onOpenGitDiff
Already addressed in a prior commit on this branch. The test at line 163–168 already dispatches a click on the wrapping <button> and asserts onOpenGitDiff was called with /tmp/project. No new change needed.

[rc:3618264957] WebShellSidebar.tsx — dead worktree option on createNewSession / handleNewSession / onNewSession
Already addressed in a prior commit on this branch. The worktree option was removed from the onNewSession prop signature, handleNewSession, and the createNewSession opts type. The onNewWorktreeSession prop was also removed from WorkspaceSection. No new change needed.

[rc:3618626603] App.tsx — no test for sidebar "New chat" clearing pending worktree intent
Implemented. Added test 'clears the pending worktree intent when starting a new session from the sidebar': enables the toggle, clicks the sidebar "New chat" button, submits a message, and verifies createSession is called without worktree.

Issue-level review (@wenshao)

1. Focus management on toggle↔badge swap (a11y)
Implemented. Added worktreeToggleRef, worktreeCancelRef, and a worktreeFocusTarget ref. A useEffect on worktreePending moves focus to the cancel button on enable and back to the toggle on cancel, so keyboard/AT users don't lose their place.

2. Git-pill accessible name includes branch (a11y)
Implemented. Changed the git-pill button's aria-label from t('gitDiff.title') (just "Changes") to `${t('gitDiff.title')} — ${gitStatus.branch}` (e.g. "Changes — main"), so the branch name is no longer dropped from the accessible name.

3. Test robustness — hardcoded promise ticks
Implemented. Replaced the three fixed flush() calls in waitForToggle() with vi.waitFor(() => expect(...).not.toBeNull()), which retries until the assertion passes regardless of how many promise ticks the git-status fetch takes.

4. Test setup — mock default in afterEachbeforeEach
Implemented. Moved the workspaceByCwd mock (returning { branch: 'main' }) from afterEach into beforeEach, so each test is self-contained and not order-dependent.

Nit: worktreeToggleEligible unmemoized
Declined — no functional impact; the reviewer noted it is "fine in practice" (cheap .find() returning a primitive). Memoizing would add a deps array for negligible gain.

Nit: role="switch" / aria-pressed on the toggle
Declined — the reviewer noted this is "Acceptable since it's a one-way reveal rather than a checkbox." Adding switch semantics would misrepresent the interaction model.

Verification

  • npx tsc --noEmit (web-shell): ✅ pass
  • npx eslint on all 3 changed files: ✅ pass
  • npx vitest run client/App.test.tsx: ✅ 150/150 pass
  • npx vitest run client/components/sidebar/WorkspaceSection.test.tsx: ✅ 7/7 pass
中文说明

已处理的审查反馈

行内评论

[rc:3618264941] WorkspaceSection.test.tsx — 测试应点击按钮并断言 onOpenGitDiff
已在此分支的先前提交中处理。测试第 163–168 行已经对包裹的 <button> 触发点击事件,并断言 onOpenGitDiff/tmp/project 被调用。无需新更改。

[rc:3618264957] WebShellSidebar.tsx — createNewSession / handleNewSession / onNewSession 上的无效 worktree 选项
已在此分支的先前提交中处理。worktree 选项已从 onNewSession 属性签名、handleNewSessioncreateNewSession 的 opts 类型中移除。onNewWorktreeSession 属性也已从 WorkspaceSection 中移除。无需新更改。

[rc:3618626603] App.tsx — 缺少侧边栏"新对话"清除待定 worktree 意图的测试
已实现。 新增测试 'clears the pending worktree intent when starting a new session from the sidebar':启用切换开关,点击侧边栏"新对话"按钮,提交消息,并验证 createSession 调用中不包含 worktree

Issue 级别审查(@wenshao

1. 切换开关↔徽章交换时的焦点管理(无障碍)
已实现。 添加了 worktreeToggleRefworktreeCancelRefworktreeFocusTarget ref。通过监听 worktreePendinguseEffect,在启用时将焦点移至取消按钮,在取消时将焦点移回切换开关,避免键盘/辅助技术用户丢失焦点位置。

2. Git 分支标签的无障碍名称包含分支名(无障碍)
已实现。 将 git 分支按钮的 aria-labelt('gitDiff.title')(仅"变更")改为 `${t('gitDiff.title')} — ${gitStatus.branch}`(例如"变更 — main"),使分支名不再从无障碍名称中丢失。

3. 测试健壮性 — 硬编码的 Promise tick
已实现。waitForToggle() 中三个固定的 flush() 调用替换为 vi.waitFor(() => expect(...).not.toBeNull()),无论 git 状态获取需要多少个 Promise tick,都会重试直到断言通过。

4. 测试设置 — mock 默认值从 afterEach 移至 beforeEach
已实现。workspaceByCwd mock(返回 { branch: 'main' })从 afterEach 移至 beforeEach,使每个测试自包含且不依赖执行顺序。

小建议:worktreeToggleEligible 未缓存
已拒绝 — 无功能影响;审查者指出"实际使用中没问题"(廉价的 .find() 返回原始值)。添加缓存会为一个微不足道的优化增加依赖数组。

小建议:切换开关使用 role="switch" / aria-pressed
已拒绝 — 审查者指出"由于这是一个单向展示而非复选框,所以可以接受"。添加 switch 语义会错误地表示交互模型。

验证结果

  • npx tsc --noEmit(web-shell):✅ 通过
  • npx eslint(所有 3 个修改文件):✅ 通过
  • npx vitest run client/App.test.tsx:✅ 150/150 通过
  • npx vitest run client/components/sidebar/WorkspaceSection.test.tsx:✅ 7/7 通过

Base-conflict check: no conflict with main.

Re-review when you have a moment. After round 100 this bot stops and leaves the PR for a human.


🧠 Handled by Qwen Code · model/模型 qwen3.8-max-preview

qwen-code-ci-bot pushed a commit that referenced this pull request Jul 21, 2026

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found. LGTM! ✅

— qwen3.7-max via Qwen Code /review

qqqys pushed a commit to qqqys/qwen-code that referenced this pull request Jul 21, 2026
…enLM#7247)

* fix(autofix): retry a model API error instead of stranding the PR

When the agent's qwen subprocess dies on a model-side [API Error]
(403 access denied, a 429 quota, a 5xx), run-agent.mjs wrote a
handoff/failure.md, so the handoff step treated it as an EVALUATED
handoff — it advanced the watermark and the next scan saw 'nothing
new', stranding the PR until a manual re-arm. But the agent never
actually evaluated the feedback; the model was unreachable.

QwenLM#7220 hit exactly this: fork-takeover engaged and ran the agent, the
model returned '[API Error: 403 Model access denied]' (the autofix key
lacks access to qwen3.8-max-preview), and the PR was left with an
advanced watermark that will not retry.

Fix, mirroring QwenLM#7229's no-output-crash handling:
- run-agent.mjs extracts a [API Error: 4xx/5xx] from the captured
  output tail, includes it in failure.md, and drops an
  marker file.
- The handoff step reads that marker and routes the failure to the
  sentinel-ts (retry) path — the watermark does NOT advance, so the
  next scan retries; the round still increments so a PERSISTENT model
  failure is bounded by MAX_ROUNDS. The headline names the model error
  and, on the final attempt, tells the maintainer to check the autofix
  model key/access and re-arm — instead of a generic crash message.

Tests: run-agent.mjs flags a model [API Error] (marker + failure.md)
and does NOT flag a generic failure; the handoff replay treats an
API-error handoff as sentinel|retry (not a watermark advance) with a
model-aware, cause-specific headline. 62/62 + 12/12.

* fix(autofix): scope + broaden the retryable model-API detection (review)

Addresses wenshao's review on QwenLM#7247:

- Behavioral (1): the agent-api-error marker was written on ANY non-zero
  exit whose output tail contained an API-error string — so a loop
  guard, a timeout, or an agent-written failure.md (a real verdict)
  would wrongly retry and, worst case, silently discard a verdict. The
  write is now scoped to the bare-failure branch and guarded by
  !timedOut, so only an un-evaluated model failure retries.
- Coverage (2): the old regex only matched a LEADING status digit, so
  it missed the canonical rate-limit render, the (Status: …) form, the
  bad-key 401, the Chinese quota text, and the unwrapped Qwen OAuth
  quota — i.e. most real errors this targets. Detection is now a
  whitelist of RECOVERABLE errors (401/402/403/429/5xx + rate-limit /
  quota / api-key / RESOURCE_EXHAUSTED / overloaded phrasings, plus the
  standalone OAuth-quota form); a 400/404 stays terminal.
- Test gap (3): a writer↔reader contract test now runs the REAL
  run-agent.mjs to write the marker, then the extracted workflow reader
  block against that same workdir — a rename on either side (proven
  with the YAML-only mutation) now fails the suite.
- Smaller: API_ERROR_DETAIL is comment-escaped (sed) and capped
  (cut -c1-200) since it derives from agent stdout; the marker match is
  single-line ([^]\n]) so a multi-line render can't smuggle a newline;
  agent-api-error is added to the run-artifacts list.

Non-recoverable 4xx (400/404) deliberately stay terminal; the live
401/403 config cases retry and self-heal once the key/access is fixed.
79/79 across both suites.

* test(autofix): cover the timeout guard and the OAuth-quota fallback (review)

Two coverage gaps from the ci-bot review on QwenLM#7247:
- The !result.timedOut guard was only asserted indirectly — no test
  emitted an [API Error] AND timed out. Added a case (spawnSync +
  QWEN_TIMEOUT_MS=100): qwen streams [API Error: 503] then hangs past
  the budget → killed → no marker. A refactor to !loopDetected now
  fails here.
- The standalone Qwen-OAuth-quota fallback (unwrapped, no [API Error:])
  had no test. Added a case emitting bare 'Qwen OAuth quota exceeded
  (limit: 100/min)' → marker written, wrapped as
  '[API Error: Qwen OAuth quota exceeded …]'.

* fix(autofix): anchor the API-error code, split retry budget by cause, keep the headline UTF-8

Addresses the review on QwenLM#7247.

Classifier (points 2 and 4): the status code is now read from its POSITION in
the render (`[API Error: <code>`) instead of matched anywhere in the message.
Matching anywhere retried permanent failures forever — `400 Invalid value for
max_tokens: must be <= 512` matched a bare \b5\d\d\b and `400 context length
exceeded` matched a bare `exceeded`. `exceeded` now only counts as part of
`quota`. A 404 whose message says the model "does not exist or you do not have
access to it" — the OpenAI-compatible render of what a 403 reports — is no
longer terminal.

Retry budget (point 3): the marker now carries the cause class. A transient
429/5xx self-heals and keeps the full round budget; an auth/access error that
only a maintainer can fix is capped at API_AUTH_MAX_ROUNDS (3) and then goes
terminal with the "check the autofix model key/access, then re-arm" headline —
instead of ~100 agent runs and ~100 PR comments over ~17h on a takeover PR.
The terminal round is stamped so the scan's round gate skips the PR while the
sentinel ts keeps the feedback live for a re-arm.

Headline (point 1): `cut -c` counts bytes under GNU coreutils and the
classifier deliberately matches CJK renders, so the 200-byte cap could split a
multi-byte character and emit invalid UTF-8. Guarded with
`iconv -f utf-8 -t utf-8 -c || true`, matching the sibling publish site (the
`|| true` is required — iconv -c exits 1 when it discards).

Minor (point 5): documented that detection is best-effort because apiError is
derived from the last 20 KB of output; `head -1` -> `head -n 1`; tests added
for a permanent 400 carrying a 3-digit number >= 500 and for a >200-byte CJK
render staying valid UTF-8.

* test(autofix): cover the auth-capped retry budget and Chinese API-error patterns (QwenLM#7247)

* fix(autofix): short-circuit 400 as terminal and classify only the last API error (QwenLM#7247)

* fix(autofix): treat transport-level API failures as retryable

QwenLM#7365 stranded at round 2/100 on this render:

    [API Error: terminated (cause: read ECONNRESET)]

The connection to the model dropped mid-run. That is as transient as a 429, but
the classifier never saw it that way: a transport failure never got far enough
to have an HTTP status, so it fell through to the keyword arm, and the keyword
arm only knew about rate limits and quotas. It was classified terminal, the
watermark advanced, and a PR that needed nothing but a re-run was handed to a
human.

Verified against the shipped classifier before the fix — every transport render
came back terminal:

    terminated (cause: read ECONNRESET)   -> terminal
    fetch failed                          -> terminal
    socket hang up                        -> terminal
    connect ETIMEDOUT                     -> terminal

Adds a transport arm to the code-less branch: ECONNRESET, ECONNREFUSED,
ETIMEDOUT, EPIPE, EAI_AGAIN, socket hang up, fetch failed, terminated.

ENOTFOUND is deliberately excluded. A hostname that does not resolve is a
misconfigured endpoint, which repeats forever — the same reasoning that keeps a
bad model name terminal.

Coded errors are unaffected: the arm sits after the status-code branch, so the
400 short-circuit added in 719991a still runs first.

* fix(autofix): address review — OAuth fallback override, comment accuracy, display clamp (QwenLM#7247)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>

@yiliang114 yiliang114 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@wenshao
wenshao added this pull request to the merge queue Jul 21, 2026
Merged via the queue into QwenLM:main with commit 2709d1f Jul 21, 2026
46 checks passed

@ytahdn ytahdn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. Suggestions are inline. Not reviewed: verification — the review posts findings, but no verifier was launched with a prompt this skill builds — they were ruled on, if at all, without the verdict bar its brief carries.

— qwen3.7-max via Qwen Code /review

Comment on lines +6010 to +6019
useEffect(() => {
if (!worktreeFocusTarget.current) return;
const target = worktreeFocusTarget.current;
worktreeFocusTarget.current = null;
if (target === 'cancel') {
worktreeCancelRef.current?.focus();
} else {
worktreeToggleRef.current?.focus();
}
}, [worktreePending]);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Suggestion] This focus management useEffect has no test coverage.

Concrete cost: If this effect is accidentally removed or the worktreeFocusTarget ref assignment is missed in a handler (e.g., handleCancelWorktree forgets to set worktreeFocusTarget.current = 'toggle'), keyboard users lose focus to <body> after clicking cancel because the original button is removed from the DOM. This is a WCAG 2.4.3 Focus Order regression that won't be caught by visual or functional tests.

Suggested fix: Add focus assertions to the existing "toggles the pending badge on and off" test:

// After enabling toggle:
expect(document.activeElement).toBe(container.querySelector('[data-testid="worktree-cancel"]'));

// After clicking cancel:
expect(document.activeElement).toBe(container.querySelector('[data-testid="worktree-toggle"]'));

— qwen3.7-max via Qwen Code /review

@ytahdn ytahdn left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed. LGTM ✅

No Critical issues found. 1 Suggestion (posted inline): focus management useEffect in App.tsx lacks test coverage — a WCAG 2.4.3 Focus Order regression could go undetected. Non-blocking.

— qwen3.7-max via Qwen Code /review

@ytahdn

ytahdn commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Code Review Summary

Verdict: Approved ✅

Statistics

  • Effort: high
  • Files: 17 (550 source, 480 test, 83 docs)
  • Build: 5 workspaces passed
  • Tests: 1230 passed (38 files, 0 failures)

Findings

  • Critical: 0
  • Suggestion: 1 (inline)
  • Nice to have: 0

Suggestion

packages/web-shell/client/App.tsx:6010-6019 — Focus management useEffect lacks test coverage. If removed or misconfigured, keyboard users could lose focus to <body> after cancel (WCAG 2.4.3 regression). Non-blocking.

Summary

Well-structured feature PR that surfaces worktree isolation in the new-session empty state. Comprehensive test coverage across unit, E2E, and visual regression layers. Code quality is good with proper state management and cleanup paths.

— qwen3.7-max via Qwen Code /review

@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.20.1.

@yiliang114

yiliang114 commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

The comment is just the release bot announcing that PR #7365 shipped in v0.20.1 — no action needed. Acknowledged, no code changes or reply required.


✅ completed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix/takeover Summon the autofix loop to manage this PR (remove to release; needs triage+)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants